/*
* Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const { XTools } = require("../XTools");

class XMat4 {
    constructor() {
        this.Unit();
    }

    Get() {
        return this.mat;
    }
    Unit() {
        this.mat = [[1, 0, 0, 0],
        [0, 1, 0, 0],
        [0, 0, 1, 0],
        [0, 0, 0, 1]];
        return this;
    }
    ForShader() {
        let tm = this.mat;
        return [
            tm[0][0], tm[1][0], tm[2][0], tm[3][0],
            tm[0][1], tm[1][1], tm[2][1], tm[3][1],
            tm[0][2], tm[1][2], tm[2][2], tm[3][2],
            tm[0][3], tm[1][3], tm[2][3], tm[3][3]
        ];
    }
    Copy(m) {
        for (let i = 0; i < 4; i++) {
            for (let j = 0; j < 4; j++) {
                this.mat[i][j] = m.mat[i][j]
            }
        }
        return this;
    }
    _init_rotate_mat_x(hd) {
        this.Unit()
        let _cos = Math.cos(hd)
        let _sin = Math.sin(hd)
        this.mat[1][1] = _cos
        this.mat[1][2] = -_sin
        this.mat[2][1] = _sin
        this.mat[2][2] = _cos
    }
    _init_rotate_mat_y(hd) {
        this.Unit()
        let _cos = Math.cos(hd)
        let _sin = Math.sin(hd)
        this.mat[0][0] = _cos
        this.mat[0][2] = -_sin
        this.mat[2][0] = _sin
        this.mat[2][2] = _cos
    }

    _init_rotate_mat_z(hd) {
        this.Unit()
        let _cos = Math.cos(hd)
        let _sin = Math.sin(hd)
        this.mat[0][0] = _cos
        this.mat[0][1] = -_sin
        this.mat[1][0] = _sin
        this.mat[1][1] = _cos
    }
    _init_scale_mat(x, y, z) {
        this.Unit()
        this.mat[0][0] = x
        this.mat[1][1] = y
        this.mat[2][2] = z
    }
    Move(x, y, z = 0) {
        this.mat[3][0] += x
        this.mat[3][1] += y
        this.mat[3][2] += z
        return this
    }
    Rotate(x, y, z) {
        if (x != 0) {
            tmp_mat._init_rotate_mat_x(-x * Math.PI / 180)
            this.Mult(tmp_mat)
        }
        if (y != 0) {
            tmp_mat._init_rotate_mat_y(y * Math.PI / 180)
            this.Mult(tmp_mat)
        }
        if (z != 0) {
            tmp_mat._init_rotate_mat_z(-z * Math.PI / 180)
            this.Mult(tmp_mat)
        }
        return this
    }

    Scale(x, y, z = 1) {
        tmp_mat._init_scale_mat(x, y, z)
        this.Mult(tmp_mat)
        return this
    }


    static Mult(mout, m1, m2) {
        let mat_out = [[1, 0, 0, 0],
        [0, 1, 0, 0],
        [0, 0, 1, 0],
        [0, 0, 0, 1]];
        let mat1 = m1.mat
        let mat2 = m2.mat

        for (let i = 0; i < 4; i++) {
            for (let j = 0; j < 4; j++) {
                mat_out[i][j] = mat1[i][0] * mat2[0][j] + mat1[i][1] * mat2[1][j] + mat1[i][2] * mat2[2][j] + mat1[i][3] * mat2[3][j];
            }
        }
        mout.mat = mat_out;
    }
    Mult(m4) {
        let tmp = [[1, 0, 0, 0],
        [0, 1, 0, 0],
        [0, 0, 1, 0],
        [0, 0, 0, 1]];
        for (let i = 0; i < 4; i++) {
            for (let j = 0; j < 4; j++) {
                tmp[i][j] = this.mat[i][0] * m4.mat[0][j] + this.mat[i][1] * m4.mat[1][j] + this.mat[i][2] * m4.mat[2][j] + this.mat[i][3] * m4.mat[3][j];
            }
        }
        this.mat = tmp;
        return this
    }

    MultRight(m4) {
        this.Copy(tmp_mat.Copy(m4).Mult(this));
        return this
    }


    PerspectiveMatrix(n, f, w = -1, h = -1) {
        if (w == -1) w = Scr.logicw
        if (h == -1) h = Scr.logich
        let ret = w / (Math.tan(30 * Math.PI / 180) * 2)
        this.Unit()
        this.mat[0][0] = 2 / w
        this.mat[1][1] = -2 / h
        this.mat[2][2] = 1 / (f - n)
        this.mat[2][3] = 1 / ret
        this.mat[3][2] = -n / (f - n)
        this.mat[3][3] = 1
        // console.log(ret,w,h)

        // this.mat[0][0] = 2 / w
        // this.mat[1][1] = 2 / h
        // this.mat[2][2] = -1 / (f - n)
        // // this.mat[2][3] = 1 / ret
        // this.mat[3][2] = -(f + n) / (f - n)
        // this.mat[3][3] = 1

        // z-up
        tmp_mat._init_rotate_mat_x(-90 * Math.PI / 180)
        this.MultRight(tmp_mat)
        return ret
    }
    OrthoMat(n, f, w = -1, h = -1) {
        if (w == -1) w = Scr.logicw
        if (h == -1) h = Scr.logich
        this.Unit()
        this.mat[0][0] = 2 / w
        this.mat[1][1] = 2 / h
        this.mat[2][2] = -2 / (f - n)
        // this.mat[3][0] = -w / 2
        // this.mat[3][1] = -h / 2
        this.mat[3][2] = -(f + n) / (f - n)
        this.mat[3][3] = 1

        // z-up
        tmp_mat._init_rotate_mat_x(90 * Math.PI / 180)
        this.MultRight(tmp_mat)
        return this
    }

    X2DMat(x, y, w, h) {
        this.Move(-w / 2 - x, -h / 2 - y, 0);
        this.Scale(2 / w, -2 / h, 0)
    }

    Make2DTransformMat(mx = 0, my = 0, sw = 1, sh = 1, ra = 0, ox = 0, oy = 0, realw = 0, realh = 0) {
        this.Unit()
        if (ox == -1) ox = 0
        if (ox == -2) ox = realw / 2
        if (ox == -3) ox = realw
        if (oy == -1) oy = 0
        if (oy == -2) oy = realh / 2
        if (oy == -3) oy = realh
        if (ox != 0 || oy != 0) this.Move(-ox, -oy, 0)
        if (sw != 1 || sh != 1) this.Scale(sw, sh, 1)
        if (ra != 0) this.Rotate(0, 0, ra)
        if (mx != 0 || my != 0) this.Move(mx, my, 0)
    }

    Make2DTransformMat_(mx, my, sw, sh, ra, ox = 0, oy = 0) {
        this.Unit()
        if (mx != 0 || my != 0) this.Move(-mx, -my, 0)
        if (ra != 0) this.Rotate(0, 0, -ra)
        if (sw != 1 || sh != 1) this.Scale(1 / sw, 1 / sh, 1)
        return this
    }

    Transform2DVertex(x, y) {
        tmp_mat.Unit();
        tmp_mat.Move(x, y, 0);
        tmp_mat.Mult(this);
        return [tmp_mat.mat[3][0], tmp_mat.mat[3][1]];
    }

    transform(x, y, z) {
        const { X3DFast } = require("./X2DInterface");
        const { Scr } = require("../XDefine");

        // tmp_mat.mat = [
        //     [x, y, z, 1],
        //     [0, 0, 0, 0],
        //     [0, 0, 0, 0],
        //     [0, 0, 0, 0]];
        // tmp_mat.Mult(this);
        // tmp_mat.Mult(X3DFast.umat);
        // return [(tmp_mat.mat[0][0] + 1) * Scr.logicw / 2, (1 - tmp_mat.mat[0][1]) * Scr.logich / 2];
        tmp_mat.Unit();
        tmp_mat.Move(x, y, z);
        tmp_mat.mat[3][3] = 1;
        tmp_mat.Mult(this);
        tmp_mat.Mult(X3DFast.umat);

        return [(tmp_mat.mat[3][0] + 1) * Scr.logicw / 2, (1 - tmp_mat.mat[3][1]) * Scr.logich / 2];
    }

    CheckMatOut(ox, oy, realw, realh) {
        let ret = {
            ox: ox,
            oy: oy,
            realw: realw,
            realh: realh,
        }
        let p1 = this.Transform2DVertex(0, 0);
        let p2 = this.Transform2DVertex(1, 0);
        let p3 = this.Transform2DVertex(0, 1);
        ret.sw = Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]));
        ret.sh = Math.sqrt((p1[0] - p3[0]) * (p1[0] - p3[0]) + (p1[1] - p3[1]) * (p1[1] - p3[1]));
        ret.ra = XTools.fAngle(p2[0] - p1[0], p2[1] - p1[1]);

        let tm = new XMat4();
        if (ox == -1) ox = 0
        if (ox == -2) ox = realw / 2
        if (ox == -3) ox = realw
        if (oy == -1) oy = 0
        if (oy == -2) oy = realh / 2
        if (oy == -3) oy = realh
        tm.Make2DTransformMat(0, 0, ret.sw, ret.sh, ret.ra, ox, oy, realw, realh);
        let p4 = tm.Transform2DVertex(0, 0);
        ret.x = p1[0] - p4[0];
        ret.y = p1[1] - p4[1];
        return ret;
    }

    static MakeMat(mat, x, y, sw = 1, sh = 1, ra = 0, ox = 0, oy = 0) {
        if (!mat) {
            mat = new XMat4();
        }
        mat.Unit();
        if (ox != 0 || oy != 0) mat.Move(-ox, -oy, 0);
        if (sw != 1 || sh != 1) mat.Scale(sw, sh, 1);
        if (ra != 0) mat.Rotate(0, 0, ra);
        if (x != 0 || y != 0) mat.Move(x, y, 0);
        return mat;
    }
    Inverse() {//逆矩阵
        let ret = new XMat4();
        ret.mat = XMat4.inv(this.mat);
        return ret;
    }
}

XMat4.det = function (square) {//行列式
    // 方阵约束
    if (square.length !== square[0].length) {
        throw new Error();
    }
    // 方阵阶数
    let n = square.length;

    let result = 0;
    if (n > 3) {
        // n 阶
        for (let column = 0; column < n; column++) {
            // 去掉第 0 行第 column 列的矩阵
            let matrix = new Array(n - 1).fill(0).map(arr => new Array(n - 1).fill(0));
            for (let i = 0; i < n - 1; i++) {
                for (let j = 0; j < n - 1; j++) {
                    if (j < column) {
                        matrix[i][j] = square[i + 1][j];
                    } else {
                        matrix[i][j] = square[i + 1][j + 1];
                    }
                }
            }
            result += square[0][column] * Math.pow(-1, 0 + column) * XMat4.det(matrix);
        }
    } else if (n === 3) {
        // 3 阶
        result = square[0][0] * square[1][1] * square[2][2] +
            square[0][1] * square[1][2] * square[2][0] +
            square[0][2] * square[1][0] * square[2][1] -
            square[0][2] * square[1][1] * square[2][0] -
            square[0][1] * square[1][0] * square[2][2] -
            square[0][0] * square[1][2] * square[2][1];
    } else if (n === 2) {
        // 2 阶
        result = square[0][0] * square[1][1] - square[0][1] * square[1][0];
    } else if (n === 1) {
        // 1 阶
        result = square[0][0];
    }
    return result;
}
XMat4.adjoint = function (square) {
    // 方阵约束
    if (square[0].length !== square.length) {
        throw new Error();
    }

    let n = square.length;

    let result = new Array(n).fill(0).map(arr => new Array(n).fill(0));
    for (let row = 0; row < n; row++) {
        for (let column = 0; column < n; column++) {
            // 去掉第 row 行第 column 列的矩阵
            let matrix = [];
            for (let i = 0; i < square.length; i++) {
                if (i !== row) {
                    let arr = [];
                    for (let j = 0; j < square.length; j++) {
                        if (j !== column) {
                            arr.push(square[i][j]);
                        }
                    }
                    matrix.push(arr);
                }
            }
            result[row][column] = Math.pow(-1, row + column) * XMat4.det(matrix);
        }
    }
    return XMat4.transpose(result);
}
XMat4.transpose = function (matrix) {
    let result = new Array(matrix.length).fill(0).map(arr => new Array(matrix[0].length).fill(0));
    for (let i = 0; i < result.length; i++) {
        for (let j = 0; j < result[0].length; j++) {
            result[i][j] = matrix[j][i];
        }
    }
    return result;
}
XMat4.inv = function (square) {
    if (square[0].length !== square.length) {
        throw new Error();
    }
    let detValue = XMat4.det(square);
    let result = XMat4.adjoint(square);

    for (let i = 0; i < result.length; i++) {
        for (let j = 0; j < result.length; j++) {
            result[i][j] /= detValue;
        }
    }
    return result;
}

var tmp_mat = new XMat4()

module.exports = {
    XMat4
}